import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.stream.Stream;

/**
 * 最接近的三数之和
 * 题目：给定一个包括n个整数的数组nums和一个目标值target。找出nums中的三个整数，使得它们的和与target最接近。返回这三个数的和。假定每组输入只存在唯一答案。
 * <p>
 * 示例：
 * 输入：nums = [-1,2,1,-4], target = 1
 * 输出：2
 * 解释：与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
 * <p>
 * 提示：
 * 3 <= nums.length <= 10^3
 * -10^3 <= nums[i] <= 10^3
 * -10^4 <= target <= 10^4
 * <p>
 * 来源：力扣（LeetCode-16）
 * 链接：https://leetcode-cn.com/problems/3sum-closest
 *
 * @author godfrey
 * @since 2020-11-02
 */
@DisplayName("最接近的三数之和")
public class ThreeSumClosest extends BaseTest {

    static Stream<Arguments> testArguments() {
        return Stream.of(
                Arguments.arguments(new int[]{-1, 2, 1, -4}, 1),
                Arguments.arguments(new int[]{-3, -2, -5, 3, -4}, -1)
        );
    }

    @DisplayName("先排序，后夹逼(双指针)-时间复杂度O(n^2)，空间复杂度O(1)")
    @ParameterizedTest
    @MethodSource("testArguments")
    void threeSumClosest(int[] nums, final int target) {
        final int len = nums.length;
        if (len < 3) {
            System.out.println(-1);
            return;
        }
        Arrays.sort(nums);
        int l, r, sum, gap;
        int res = 0, minGap = Integer.MAX_VALUE;
        for (int i = 0; i < len - 1; ++i) {
            l = i + 1;
            r = len - 1;
            while (l < r) {
                sum = nums[i] + nums[l] + nums[r];
                gap = Math.abs(sum - target);
                if (gap < minGap) {
                    res = sum;
                    minGap = gap;
                }
                if (sum < target) {
                    ++l;
                } else if (sum > target) {
                    --r;
                } else {
                    System.out.println(res);
                    return;
                }
            }

        }
        System.out.println(res);
    }

    @DisplayName("双指针优化-时间复杂度O(n^2)，空间复杂度O(1)")
    @ParameterizedTest
    @MethodSource("testArguments")
    void threeSumClosest2(int[] nums, final int target) {
        final int len = nums.length;
        if (len < 3) {
            System.out.println(-1);
            return;
        }
        Arrays.sort(nums);
        int l, r, sum, gap;
        int res = 0, minGap = Integer.MAX_VALUE;
        for (int i = 0; i < len - 1; ++i) {
            //解决num[i]重复
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            l = i + 1;
            r = len - 1;
            while (l < r) {
                sum = nums[i] + nums[l] + nums[r];
                gap = Math.abs(sum - target);
                if (gap < minGap) {
                    res = sum;
                    minGap = gap;
                }
                if (sum < target) {
                    ++l;
                    //解决num[l]重复
                    while (l < r && nums[l] == nums[l - 1]) {
                        ++l;
                    }
                } else if (sum > target) {
                    --r;
                    //解决num[r]重复
                    while (l < r && nums[r] == nums[r + 1]) {
                        --r;
                    }
                } else {
                    System.out.println(res);
                    return;
                }
            }

        }
        System.out.println(res);
    }
}
